package request

import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"errors"
	"gitee.com/huanminabc/go-utils/file"
	log "github.com/sirupsen/logrus"
	"golang.org/x/net/html/charset"
	"io"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"strings"
	"time"
)

type HttpClient struct {
	Client   *http.Client
	Response *http.Response
	Error    error

	req       *http.Request
	Url       string
	params    map[string]string
	formData  map[string]string
	HeaderMap map[string]string
	data      interface{}
	method    string
	retry     int
	retryTime int //重试时间 单位ms

}

// timeout 单位秒 请求的超时时间
func NewHttpClient(timeout int) *HttpClient {
	return &HttpClient{
		Client: &http.Client{
			Timeout: time.Duration(timeout) * time.Second,
		},
		params:   make(map[string]string),
		formData: make(map[string]string),
		HeaderMap: map[string]string{
			// 默认json请求 ,在有些网站请求html内容的时候是不支持直接使用json格式的请求的
			// 需要手动设置为AddHeader("Content-Type", "text/html; charset=utf-8")
			"Content-Type": "application/json",
			//"User-Agent":   browser.Chrome(), // 随机生成浏览器信息 , 这个有点假,很多网站能识别出来,因为版本啥的都是随机的
		},
	}
}

func (httpClient *HttpClient) UserAgentPc() *HttpClient {
	var userAgent = []string{
		"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
		"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0",
		"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
	}
	//随机获取一个
	httpClient.HeaderMap["User-Agent"] = userAgent[time.Now().Unix()%int64(len(userAgent))]
	return httpClient
}

// 添加客户端配置
func (httpClient *HttpClient) AddCConfig(c *http.Client) *HttpClient {
	httpClient.Client = c
	return httpClient
}

// 设置请求超时时间
func (httpClient *HttpClient) SetTimeout(timeout time.Duration) *HttpClient {
	httpClient.Client.Timeout = timeout
	return httpClient
}

// "http://proxy-server-address:port"   用户密码可以为空
func (httpClient *HttpClient) AddProxy(proxyUrl, username, password string) *HttpClient {
	proxyUrl1, err := url.Parse(proxyUrl)
	if err != nil {
		panic(err)
	}
	if username != "" && password != "" {
		proxyUrl1.User = url.UserPassword(username, password)
	}
	httpClient.Client.Transport = &http.Transport{
		Proxy: http.ProxyURL(proxyUrl1),
	}
	return httpClient
}

// 请添加cookie
func (httpClient *HttpClient) AddCookie(cookie *http.Cookie) *HttpClient {
	httpClient.Client.Jar.SetCookies(httpClient.req.URL, []*http.Cookie{cookie})
	return httpClient
}

// 客户端跳过服务端的https证书验证
func (httpClient *HttpClient) SkipVerify() *HttpClient {
	httpClient.Client.Transport = &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	return httpClient
}

// 双向认证及客户端会验证服务端的证书   certFile:客户端证书client.pem  keyFile:客户端私钥 client.key
func (httpClient *HttpClient) AddCert(certFile, keyFile string) *HttpClient {
	clientCertPool := x509.NewCertPool()
	certBytes, err := os.ReadFile(certFile)
	if err != nil {
		panic("Unable to read cert.pem")
	}
	// 解析证书
	ok := clientCertPool.AppendCertsFromPEM(certBytes)
	if !ok {
		panic("failed to parse root certificate")
	}
	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		panic(err)
	}
	httpClient.Client.Transport = &http.Transport{
		TLSClientConfig: &tls.Config{
			RootCAs:      clientCertPool,
			Certificates: []tls.Certificate{cert},
		},
	}
	return httpClient
}

// 添加请求头
func (httpClient *HttpClient) AddHeader(key, value string) *HttpClient {
	httpClient.HeaderMap[key] = value
	return httpClient
}

// 添加请求参数
func (httpClient *HttpClient) AddParams(key, value string) *HttpClient {
	httpClient.params[key] = value
	return httpClient
}

// 添加请求参数
func (httpClient *HttpClient) FormUrlencoded(key, value string) *HttpClient {
	httpClient.AddHeader("Content-Type", "application/x-www-form-urlencoded")
	httpClient.formData[key] = value
	return httpClient
}
func (httpClient *HttpClient) AddFormData(key, value string) *HttpClient {
	httpClient.AddHeader("Content-Type", "multipart/form-data")
	httpClient.formData[key] = value
	return httpClient

}

// 添加body数据
func (httpClient *HttpClient) AddData(data interface{}) *HttpClient {
	httpClient.AddHeader("Content-Type", "application/json")
	httpClient.data = data
	return httpClient
}

// 添加请求url
func (httpClient *HttpClient) AddUrl(url1 string) *HttpClient {
	httpClient.Url = url1
	return httpClient
}

// 手动设置请求方法
func (httpClient *HttpClient) SetMethod(method string) *HttpClient {
	httpClient.method = method
	return httpClient
}

// get请求
func (httpClient *HttpClient) Get() *HttpClient {
	httpClient.method = "GET"
	return httpClient
}

// post请求
func (httpClient *HttpClient) Post() *HttpClient {
	httpClient.method = "POST"
	return httpClient
}

// put请求
func (httpClient *HttpClient) Put() *HttpClient {
	httpClient.method = "PUT"
	return httpClient
}

// delete请求
func (httpClient *HttpClient) Delete() *HttpClient {
	httpClient.method = "DELETE"
	return httpClient
}

// 添加失败重试次数
func (httpClient *HttpClient) AddRetry(retry int, retryTime int) *HttpClient {
	httpClient.retry = retry
	httpClient.retryTime = retryTime
	return httpClient
}

// 同步
func (httpClient *HttpClient) Sync() *HttpClient {
	do, err := httpClient.do()
	if err != nil {
		httpClient.Error = err
		return httpClient
	}
	httpClient.Response = do
	return httpClient
}

// 异步
func (httpClient *HttpClient) Async(fun func(res *HttpClient)) {
	go func() {
		do, err := httpClient.do()
		if err != nil {
			httpClient.Error = err
			return
		}
		httpClient.Response = do
		fun(httpClient)
	}()
}

// 结果转字符串
func (httpClient *HttpClient) ToString() (string, error) {
	if httpClient.Error != nil {
		return "", httpClient.Error
	}
	body := httpClient.Response.Body
	defer body.Close()
	all, err := io.ReadAll(body)
	if err != nil {
		return "", err
	}
	return string(all), nil
}

// 自动解决读取内容的编码乱码问题, 比如读取的html内容是gbk编码的,那么这个方法会自动转换为utf-8
func (httpClient *HttpClient) ToDecode() *HttpClient {
	if httpClient.Response == nil {
		return httpClient
	}
	body := httpClient.Response.Body
	all, err := io.ReadAll(body)
	if err != nil {
		httpClient.Error = err
		return httpClient
	}
	// 使用 charset 包来确定和转换字符集
	ctype := httpClient.Response.Header.Get("Content-Type")
	if ctype == "" {
		return httpClient
	}
	e, _, _ := charset.DetermineEncoding(all, ctype)
	reader := e.NewDecoder().Reader(bytes.NewReader(all))
	httpClient.Response.Body = io.NopCloser(reader)
	return httpClient
}

func (httpClient *HttpClient) Upload(fileKey, fileName string, bytes []byte) {
	file.Upload(httpClient.Url, fileKey, fileName, bytes, httpClient.formData)
}

// 上传文件
func (httpClient *HttpClient) UploadFile(fileKey, filePath string) {
	file.UploadFile(httpClient.Url, fileKey, filePath, httpClient.formData)
}

// 批量上传文件
func (httpClient *HttpClient) UploadFiles(fileKey string, filePaths []string) {
	file.UploadFiles(httpClient.Url, fileKey, filePaths, httpClient.formData)
}

// 下载文件返回字节
func (httpClient *HttpClient) Download() []byte {
	return file.Download(httpClient.Url)
}

// 下载文件
func (httpClient *HttpClient) DownloadFile(filePath string) {
	file.DownloadFile(httpClient.Url, filePath)
}

// 发起请求
func (httpClient *HttpClient) do() (*http.Response, error) {
	err := httpClient.configRequestContent()
	if err != nil {
		return nil, err
	}
	//设置请求内容
	for i := 1; i <= httpClient.retry; i++ {
		log.Error(httpClient.Url+"请求失败，正在重试", i, "次")
		response, err1 := httpClient.Client.Do(httpClient.req)
		if err1 != nil {
			//如果是最后一次那么记录err
			if i == httpClient.retry {
				return nil, err1
			}
			time.Sleep(time.Duration(httpClient.retryTime) * time.Millisecond)
			continue
		}
		//不是200也重试
		if response.StatusCode != 200 {
			if i == httpClient.retry {
				return nil, errors.New(httpClient.Url + "请求失败" + response.Status)
			}
			time.Sleep(time.Duration(httpClient.retryTime) * time.Millisecond)
			continue
		}
		return response, nil
	}
	return nil, err
}

func convertDataToJson(data interface{}) (string, error) {
	if data == nil {
		return "{}", nil
	}
	//判断data是否为结构体类型,map类型，切片类型，数组类型,如果是则转换为json字符串
	kind := reflect.TypeOf(data).Kind()
	if kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice || kind == reflect.Array {
		bodyJsonByte, err := json.Marshal(data)
		if err != nil {
			return "", err
		}
		bodyJsonStr := string(bodyJsonByte)
		if bodyJsonStr == "" || bodyJsonStr == "null" || bodyJsonStr == "NULL" || bodyJsonStr == "nil" {
			bodyJsonStr = "{}"
		}
		return bodyJsonStr, nil
	}

	if kind == reflect.String {
		return data.(string), nil
	}

	return "", errors.New("data type is not support")
}

func (httpClient *HttpClient) configRequestContent() error {
	//init url params
	requestUrl, err := initParams(httpClient.Url, httpClient.params)
	if err != nil {
		return err
	}
	//init header
	v, ok := httpClient.HeaderMap["Content-Type"]
	if ok && v == "application/json" {
		data, err := convertDataToJson(httpClient.data)
		if err != nil {
			return err
		}
		httpClient.req, err = initHeader(requestUrl, httpClient.HeaderMap, httpClient.method, strings.NewReader(data))
		if err != nil {
			return err
		}
		return nil
	}

	if ok && v == "application/x-www-form-urlencoded" {
		data := convertDataToJsonForForm(httpClient.formData)
		httpClient.req, err = initHeader(requestUrl, httpClient.HeaderMap, httpClient.method, strings.NewReader(data.Encode()))
		if err != nil {
			return err
		}
		return nil
	}

	//"multipart/form-data"
	//调用uploadxx方法会自动处理相关的信息
	if ok && v == "multipart/form-data" {
		panic("multipart/form-data 文件上传请调用uploadxx方法")
	}
	httpClient.req, err = initHeader(requestUrl, httpClient.HeaderMap, httpClient.method, nil)
	return nil
}

// 将map转换为url.Values 用于application/x-www-form-urlencoded
func convertDataToJsonForForm(maps map[string]string) *url.Values {
	values := url.Values{}
	if maps != nil {
		for key, value := range maps {
			values.Set(key, value)
		}
	}

	return &values
}

func initParams(urlPath string, params map[string]string) (*url.URL, error) {
	if params == nil {
		return url.ParseRequestURI(urlPath)
	}
	urlParams := url.Values{}
	for key, value := range params {
		urlParams.Set(key, value)
	}
	requestURI, err := url.ParseRequestURI(urlPath)
	if err != nil {
		return nil, err
	}
	requestURI.RawQuery = urlParams.Encode()

	return requestURI, nil
}

func initHeader(requestUrl *url.URL, headers map[string]string, method string, body io.Reader) (*http.Request, error) {
	//为了防止把/#/ 给转义了导致vue请求失败,我们需要手动替换回来
	path := requestUrl.String()
	if strings.Contains(path, "/%23/") {
		//替换第一个
		path = strings.Replace(path, "/%23/", "/#/", 1)
	}

	req, err := http.NewRequest(method, path, body)
	if err != nil {
		return nil, err
	}

	//set header
	for key, value := range headers {
		req.Header.Add(key, value)
	}

	return req, nil
}
